#!/bin/sh
#-*-Perl-*-

exec perl -w -x $0 "$@"

#!perl

##############################################################################
#
# jabberd - perl wrapper script to manage launching and controlling the various
#           binaries that make up the 2.0 version of the jabberd server.
#
##############################################################################

use strict;
use Getopt::Std;
use FileHandle;
use IPC::Open3;
use IO::Select;
use POSIX;
use POSIX qw(setsid);


#-----------------------------------------------------------------------------
# Define some initial variables and default them as needed.
#-----------------------------------------------------------------------------
my $Bin = "@bindir@";
my $LibExec = "@libexecdir@";
my $VERSION = "@VERSION@";
my $config_dir = "@sysconfdir@";
my $config = $config_dir."/jabberd.cfg";
$config = "internal" unless (-e $config);
my $debug = 0;
my $daemon = 0;
my $g_kill_signal = "";
my $select = IO::Select->new();
my ($exe) = ($0 =~ /([^\/]+)$/);
my %jobs;
my %fhs;
my @programs;


#-----------------------------------------------------------------------------
# Process the command line arguments
#-----------------------------------------------------------------------------
my %opts;
getopts("c:Dhb",\%opts);
&usage if exists($opts{h});
if (exists($opts{c}))
{
    $config = $opts{c} if (defined($opts{c}) && ($opts{c} ne ""));
    &usage() if (!defined($opts{c}) || ($opts{c} eq ""));
}

$debug = 1 if exists($opts{D});
$daemon = 1 if exists($opts{b});

#-----------------------------------------------------------------------------
# Catch some signals so that we can handle them later.
#-----------------------------------------------------------------------------
$SIG{HUP} = \&Signal;
$SIG{TERM} = \&Signal;
$SIG{INT} = \&Signal;
$SIG{CHLD} = "IGNORE";


#-----------------------------------------------------------------------------
# Setup the jobs: router, sm, c2s, s2s
#-----------------------------------------------------------------------------
$jobs{jabberd}->{prefix}  = "JBRD";

$jobs{router}->{cmd} = "$Bin/router";
$jobs{router}->{config} = "$config_dir/router.xml";
$jobs{router}->{prefix} = "ROUT";

$jobs{sm}->{cmd} = "$Bin/sm";
$jobs{sm}->{config} = "$config_dir/sm.xml";
$jobs{sm}->{prefix} = "SM";

$jobs{c2s}->{cmd} = "$Bin/c2s";
$jobs{c2s}->{config} = "$config_dir/c2s.xml";
$jobs{c2s}->{prefix} = "C2S";

$jobs{s2s}->{cmd} = "$Bin/s2s";
$jobs{s2s}->{config} = "$config_dir/s2s.xml";
$jobs{s2s}->{prefix} = "S2S";

if ($config eq "internal")
{
    $programs[0] = ["router"];
    $programs[1] = ["sm"];
    $programs[2] = ["c2s"];
    $programs[3] = ["s2s"];
}
else
{
    if (!(-f $config))
    {
        print "ERROR: config file does not exist: $config\n";
        exit(1);
    }
    open(CFG,$config);
    while(<CFG>)
    {
        next if /^\#/;
        next if /^\s*$/;
        my ($job,$config) = /^\s*(\S+)\s*(\S*)\s*$/;
        # Assume that all the commands are in the same directory
        # as the jabberd script. The current configuration file
        # format does not allow specification of pathnames for commands.
        #my $cmd = "$Bin/$job";
        my $cmd = $jobs{$job}->{cmd};
        push(@programs,[$job,$config,$cmd]);
    }
    close(CFG);
}

if ($debug)
{
    &debug("jabberd","stdout","debug on\n");
    &debug("jabberd","stdout","version($VERSION)\n");
    &debug("jabberd","stdout","config_dir($config_dir)\n");
}

#-----------------------------------------------------------------------------
# Launch all of the jobs.
#-----------------------------------------------------------------------------
if ($#programs == -1)
{
    print "ERROR: No jobs to launch.\n";
    exit(1);
}


foreach my $job (@programs)
{
    &LaunchJob($job->[0],$job->[1],$job->[2]);
}

unless (!$daemon || $debug)
{
    # Fork and become a daemon. Exit if we are the parent process.
    defined(my $pid = fork()) || die "Could not fork: $!";
    POSIX:_exit(0) if $pid;
    # If we are the child process, continue (but act like a daemon).
    setsid or die "Could not start a new POSIX Session: $!";
    chdir "/" or die "Could not chdir to /: $!";
    umask 0;
    open STDIN, "/dev/null" or die "Could not set STDIN to /dev/null: $!";
    open STDOUT, "/dev/null" or die "Could not set STDOUT to /dev/null: $!";
    open STDERR, "/dev/null" or die "Could not set STDERR to /dev/null: $!";
}

#-----------------------------------------------------------------------------
# Run the main loop.  Read the output from the jobs, watch for dead jobs and
# restart them, make sure that the debug output is clearly marked.
#-----------------------------------------------------------------------------
while (1)
{
    my @ready = $select->can_read();
    if ($g_kill_signal ne "") {
        if (($g_kill_signal eq "INT") || ($g_kill_signal eq "TERM"))
        {
            &Shutdown($g_kill_signal);
        } else {
            &PassSignal($g_kill_signal);
        }
        $g_kill_signal = "";
    }
    foreach my $fh (@ready)
    {
        my $line = <$fh>;
        if (defined($line))
        {
            &debug($fhs{$fh}->{job},$fhs{$fh}->{std},$line);
        }
        else
        {
            print "ERROR: $fhs{$fh}->{job} died.  Shutting down server.\n";
            &Shutdown;
        }
    }
}



##############################################################################
#
# LaunchJob - Do all of the necessary steps to monitor the job and launch it.
#
##############################################################################
sub LaunchJob
{
    my $job = shift;
    my $config = shift;
    my $cmd = shift;

    if (!defined($cmd) || $cmd eq "")
    {
        $cmd = $jobs{$job}->{cmd};
        if ($cmd eq "") {
            print "cmd is empty, cannot launch\n";
            return;
        }
    }

    if (defined($config))
    {
        $cmd .= " -c ".$config;
    }
    elsif (defined($jobs{$job}->{config}))
    {
        $cmd .= " -c ".$jobs{$job}->{config};
    }

    if ($debug && $jobs{$job}->{prefix} ne "MUC") {
        $cmd .= " -D";
    }
    if (defined($jobs{$job}->{args}) && ($jobs{$job}->{args} ne "")) {
        $cmd .= " ".$jobs{$job}->{args};
    }
    
    &debug("jabberd","stdout","LaunchJob: $job -> $cmd\n");

    &CloseJob($job) if exists($jobs{$job}->{launched});
    
    $jobs{$job}->{stdout} = new FileHandle();
    $jobs{$job}->{stderr} = new FileHandle();

    $jobs{$job}->{stdout}->autoflush(1);
    $jobs{$job}->{stderr}->autoflush(1);

    my $stdin = new FileHandle();
    $jobs{$job}->{pid} = open3($stdin,
                               $jobs{$job}->{stdout},
                               $jobs{$job}->{stderr},
                               $cmd);
    
    print $stdin "\n";
    $stdin->close();
    
    $select->add($jobs{$job}->{stdout});
    $select->add($jobs{$job}->{stderr});

    $jobs{$job}->{launched} = 1;

    $fhs{$jobs{$job}->{stdout}}->{job} = $job;
    $fhs{$jobs{$job}->{stdout}}->{std} = "stdout";
    
    $fhs{$jobs{$job}->{stderr}}->{job} = $job;
    $fhs{$jobs{$job}->{stderr}}->{std} = "stderr";
}


##############################################################################
#
# CloseJob - Do all of the necessary steps to clean up after a job.
#
##############################################################################
sub CloseJob
{
    my $job = shift;

    $select->remove($jobs{$job}->{stdout},
                    $jobs{$job}->{stderr});
    
    $jobs{$job}->{stdout}->close();
    $jobs{$job}->{stderr}->close();

    delete($jobs{$job}->{launched});
}


##############################################################################
#
# Signal - when we get a signal... we need to do something about it.
#
##############################################################################
sub Signal
{
    $g_kill_signal = shift;
}

sub PassSignal
{
    my $sig = shift;
    if ((! defined($sig)) || ($sig eq "")) {
        return;
    }
        
    foreach my $job (keys(%jobs))
    {
        next unless exists($jobs{$job}->{launched});
        &debug("jabberd","stdout","sending $sig to $jobs{$job}->{pid} : $job\n");
        kill $sig => $jobs{$job}->{pid};
    }
}

sub Shutdown
    {
    my $sig = shift;
    if (! defined($sig)) {
        $sig = "TERM";
    }

    PassSignal($sig);

    &debug("Shutting down.\n");
    my $ret;
    foreach my $job(keys(%jobs)) {
        $ret = waitpid($jobs{$job}->{pid}, 0) if defined($jobs{$job}->{pid});
}

    exit(0);
}

##############################################################################
#
# debug - print out a message for debug.  Making sure that the prefix is the
#         program that generated the debug.
#
##############################################################################
sub debug
{
    return unless $debug;
    my $job = shift;
    my $std = shift;

    #my $flag = " ";
    #$flag = "*" if ($std eq "stderr");
    #printf("%s%-4s: ",$flag,$jobs{$job}->{prefix});

    my $prefix = $jobs{$job}->{prefix};
    $prefix = $job if ! defined($prefix);
    printf("%-4s: ",$prefix);
    print join("",@_);
}


##############################################################################
#
# usage - print out the help and exit
#
##############################################################################
sub usage
{
    print "$exe - jabberd wrapper script ($VERSION)\n";
    print "Usage: $exe <options>\n";
    print "Options are:\n";
    print "   -c <file>  config file to use [default: $config]\n";
    print "   -D         Show debug output\n";
    print "   -b         Push into background\n";
    print "   -h         Show this help\n";
    exit(0);
}

